/*******************************************************************************
 * Copyright (c) 2000, 2019 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Serge Beauchamp (Freescale Semiconductor) - Bug 229633
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 472784
 *     Patrik Suzzi <psuzzi@gmail.com> - Bug 489250
 *******************************************************************************/
package org.eclipse.ui.actions;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.net.URI;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.Adapters;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.IOverwriteQuery;
import org.eclipse.ui.ide.dialogs.ImportTypeDialog;
import org.eclipse.ui.ide.undo.AbstractWorkspaceOperation;
import org.eclipse.ui.ide.undo.CopyResourcesOperation;
import org.eclipse.ui.ide.undo.WorkspaceUndoUtil;
import org.eclipse.ui.internal.ide.IDEInternalPreferences;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.ide.StatusUtil;
import org.eclipse.ui.internal.ide.dialogs.IDEResourceInfoUtils;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.ui.wizards.datatransfer.FileStoreStructureProvider;
import org.eclipse.ui.wizards.datatransfer.ImportOperation;

/**
 * Perform the copy of file and folder resources from the clipboard when paste
 * action is invoked.
 * <p>
 * This class may be instantiated; it is not intended to be subclassed.
 * </p>
 * @noextend This class is not intended to be subclassed by clients.
 */
public class CopyFilesAndFoldersOperation {

	/**
	 * Status containing the errors detected when running the operation or
	 * <code>null</code> if no errors detected.
	 */
	private MultiStatus errorStatus;

	/**
	 * The parent shell used to show any dialogs.
	 */
	private Shell messageShell;

	/**
	 * Whether or not the copy has been canceled by the user.
	 */
	private boolean canceled = false;

	/**
	 * Whether or not the operation creates virtual folders and links instead of folders
	 * and files.
	 */
	private boolean createVirtualFoldersAndLinks = false;

	/**
	 * Whether or not the operation creates links instead of folders and files.
	 */
	private boolean createLinks = false;

	private String relativeVariable = null;
	/**
	 * Overwrite all flag.
	 */
	private boolean alwaysOverwrite = false;

	private String[] modelProviderIds;

	/**
	 * Returns a new name for a copy of the resource at the given path in the
	 * given workspace. This name is determined automatically.
	 *
	 * @param originalName
	 *            the full path of the resource
	 * @param workspace
	 *            the workspace
	 * @return the new full path for the copy
	 */
	static IPath getAutoNewNameFor(IPath originalName, IWorkspace workspace) {
		String resourceName = originalName.lastSegment();
		IPath leadupSegment = originalName.removeLastSegments(1);
		boolean isFile = !originalName.hasTrailingSeparator();

		String newName = computeNewName(resourceName, isFile);
		while (true) {
			IPath pathToTry = leadupSegment.append(newName);
			if (!workspace.getRoot().exists(pathToTry)) {
				return pathToTry;
			}
			newName = computeNewName(newName, isFile);
		}
	}

	private static String computeNewName(String str, boolean isFile) {
		int lastIndexOfDot = str.lastIndexOf('.');
		String fileExtension = ""; //$NON-NLS-1$
		String fileNameNoExtension = str;
		if (isFile && lastIndexOfDot > 0) {
			fileExtension = str.substring(lastIndexOfDot);
			fileNameNoExtension = str.substring(0, lastIndexOfDot);
		}
		Pattern p = Pattern.compile("[0-9]+$"); //$NON-NLS-1$
		Matcher m = p.matcher(fileNameNoExtension);
		if (m.find()) {
			// String ends with a number: increment it by 1
			String numberStr;
			BigDecimal newNumber = null;
			try {
				newNumber = new BigDecimal(m.group()).add(BigDecimal.valueOf(1));
				numberStr = m.replaceFirst(newNumber.toPlainString());
			} catch (NumberFormatException e) {
				numberStr = m.replaceFirst("2"); //$NON-NLS-1$
			}
			return numberStr + fileExtension;
		}
		return fileNameNoExtension + "2" + fileExtension; //$NON-NLS-1$
	}

	/**
	 * Creates a new operation initialized with a shell.
	 *
	 * @param shell
	 *            parent shell for error dialogs
	 */
	public CopyFilesAndFoldersOperation(Shell shell) {
		messageShell = shell;
	}

	/**
	 * Returns whether this operation is able to perform on-the-fly
	 * auto-renaming of resources with name collisions.
	 *
	 * @return <code>true</code> if auto-rename is supported, and
	 *         <code>false</code> otherwise
	 */
	protected boolean canPerformAutoRename() {
		return true;
	}

	/**
	 * Returns the message for querying deep copy/move of a linked resource.
	 *
	 * @param source
	 *            resource the query is made for
	 * @return the deep query message
	 */
	protected String getDeepCheckQuestion(IResource source) {
		return NLS
				.bind(
						IDEWorkbenchMessages.CopyFilesAndFoldersOperation_deepCopyQuestion,
						source.getFullPath().makeRelative());
	}

	/**
	 * Checks whether the infos exist.
	 *
	 * @param stores
	 *            the file infos to test
	 * @return Multi status with one error message for each missing file.
	 */
	IStatus checkExist(IFileStore[] stores) {
		MultiStatus multiStatus = new MultiStatus(PlatformUI.PLUGIN_ID,
				IStatus.OK, getProblemsMessage(), null);

		for (IFileStore store : stores) {
			if (store.fetchInfo().exists() == false) {
				String message = NLS.bind(IDEWorkbenchMessages.CopyFilesAndFoldersOperation_resourceDeleted,
								store.getName());
				IStatus status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, message, null);
				multiStatus.add(status);
			}
		}
		return multiStatus;
	}

	/**
	 * Checks whether the resources with the given names exist.
	 *
	 * @param resources
	 *            IResources to checl
	 * @return Multi status with one error message for each missing file.
	 */
	IStatus checkExist(IResource[] resources) {
		MultiStatus multiStatus = new MultiStatus(PlatformUI.PLUGIN_ID,
				IStatus.OK, getProblemsMessage(), null);

		for (IResource resource : resources) {
			if (resource != null && !resource.isVirtual()) {
				URI location = resource.getLocationURI();
				String message = null;
				if (location != null) {
					IFileInfo info = IDEResourceInfoUtils.getFileInfo(location);
					if (info == null || info.exists() == false) {
						if (resource.isLinked()) {
							message = NLS
									.bind(
											IDEWorkbenchMessages.CopyFilesAndFoldersOperation_missingLinkTarget,
											resource.getName());
						} else {
							message = NLS
									.bind(
											IDEWorkbenchMessages.CopyFilesAndFoldersOperation_resourceDeleted,
											resource.getName());
						}
					}
				}
				if (message != null) {
					IStatus status = new Status(IStatus.ERROR,
							PlatformUI.PLUGIN_ID, IStatus.OK, message, null);
					multiStatus.add(status);
				}
			}
		}
		return multiStatus;
	}

	/**
	 * Check if the user wishes to overwrite the supplied resource or all
	 * resources.
	 *
	 * @param source
	 *            the source resource
	 * @param destination
	 *            the resource to be overwritten
	 * @return one of IDialogConstants.YES_ID, IDialogConstants.YES_TO_ALL_ID,
	 *         IDialogConstants.NO_ID, IDialogConstants.CANCEL_ID indicating
	 *         whether the current resource or all resources can be overwritten,
	 *         or if the operation should be canceled.
	 */
	private int checkOverwrite(final IResource source,
			final IResource destination) {
		final int[] result = new int[1];

		// Dialogs need to be created and opened in the UI thread
		Runnable query = () -> {
			String message;
			int resultId[] = { IDialogConstants.YES_ID,
					IDialogConstants.YES_TO_ALL_ID, IDialogConstants.NO_ID,
					IDialogConstants.CANCEL_ID };
			String labels[] = new String[] { IDialogConstants.YES_LABEL,
					IDialogConstants.YES_TO_ALL_LABEL,
					IDialogConstants.NO_LABEL,
					IDialogConstants.CANCEL_LABEL };

			if (destination.getType() == IResource.FOLDER) {
				if (homogenousResources(source, destination)) {
					message = NLS
							.bind(
									IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteMergeQuestion,
									destination.getFullPath()
									.makeRelative());
				} else {
					if (destination.isLinked()) {
						message = NLS
								.bind(
										IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteNoMergeLinkQuestion,
										destination.getFullPath()
										.makeRelative());
					} else {
						message = NLS
								.bind(
										IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteNoMergeNoLinkQuestion,
										destination.getFullPath()
										.makeRelative());
					}
					resultId = new int[] { IDialogConstants.YES_ID,
							IDialogConstants.NO_ID,
							IDialogConstants.CANCEL_ID };
					labels = new String[] { IDialogConstants.YES_LABEL,
							IDialogConstants.NO_LABEL,
							IDialogConstants.CANCEL_LABEL };
				}
			} else {
				String[] bindings = new String[] {
						IDEResourceInfoUtils.getLocationText(destination),
						IDEResourceInfoUtils
						.getDateStringValue(destination),
						IDEResourceInfoUtils.getLocationText(source),
						IDEResourceInfoUtils.getDateStringValue(source) };
				message = NLS
						.bind(
								IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteWithDetailsQuestion,
								bindings);
			}
			MessageDialog dialog = new MessageDialog(
					messageShell,
					IDEWorkbenchMessages.CopyFilesAndFoldersOperation_resourceExists,
					null, message, MessageDialog.QUESTION, 0, labels) {
				@Override
				protected int getShellStyle() {
					return super.getShellStyle() | SWT.SHEET;
				}
			};
			dialog.open();
			if (dialog.getReturnCode() == SWT.DEFAULT) {
				// A window close returns SWT.DEFAULT, which has to be
				// mapped to a cancel
				result[0] = IDialogConstants.CANCEL_ID;
			} else {
				result[0] = resultId[dialog.getReturnCode()];
			}
		};
		messageShell.getDisplay().syncExec(query);
		return result[0];
	}

	/**
	 * Recursively collects existing files in the specified destination path.
	 *
	 * @param destinationPath
	 *            destination path to check for existing files
	 * @param copyResources
	 *            resources that may exist in the destination
	 * @param existing
	 *            holds the collected existing files
	 */
	private void collectExistingReadonlyFiles(IPath destinationPath,
			IResource[] copyResources, ArrayList<IFile> existing) {
		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();

		for (IResource resource : copyResources) {
			IPath newDestinationPath = destinationPath.append(resource.getName());
			IResource newDestination = workspaceRoot.findMember(newDestinationPath);
			IFolder folder;

			if (newDestination == null) {
				continue;
			}
			folder = getFolder(newDestination);
			if (folder != null) {
				IFolder sourceFolder = getFolder(resource);

				if (sourceFolder != null) {
					try {
						collectExistingReadonlyFiles(newDestinationPath,
								sourceFolder.members(), existing);
					} catch (CoreException exception) {
						recordError(exception);
					}
				}
			} else {
				IFile file = getFile(newDestination);

				if (file != null) {
					if (file.isReadOnly()) {
						existing.add(file);
					}
					if (getValidateConflictSource()) {
						IFile sourceFile = getFile(resource);
						if (sourceFile != null) {
							existing.add(sourceFile);
						}
					}
				}
			}
		}
	}

	/**
	 * Copies the resources to the given destination. This method is called
	 * recursively to merge folders during folder copy.
	 *
	 * @param resources
	 *            the resources to copy
	 * @param destination
	 *            destination to which resources will be copied
	 * @param monitor
	 *            a progress monitor for showing progress and for cancelation
	 *
	 * @deprecated As of 3.3, the work is performed in the undoable operation
	 *             created in
	 *             {@link #getUndoableCopyOrMoveOperation(IResource[], IPath)}
	 */
	@Deprecated
	protected void copy(IResource[] resources, IPath destination, IProgressMonitor monitor) throws CoreException {
		SubMonitor subMonitor = SubMonitor.convert(monitor,
				IDEWorkbenchMessages.CopyFilesAndFoldersOperation_CopyResourcesTask, resources.length);

		for (IResource resource : resources) {
			SubMonitor iterationMonitor = subMonitor.split(1).setWorkRemaining(100);
			IPath destinationPath = destination.append(resource.getName());
			IWorkspace workspace = resource.getWorkspace();
			IWorkspaceRoot workspaceRoot = workspace.getRoot();
			IResource existing = workspaceRoot.findMember(destinationPath);
			if (resource.getType() == IResource.FOLDER && existing != null) {
				// the resource is a folder and it exists in the destination,
				// copy the
				// children of the folder.
				if (homogenousResources(resource, existing)) {
					IResource[] children = ((IContainer) resource).members();
					copy(children, destinationPath, iterationMonitor.split(100));
				} else {
					// delete the destination folder, copying a linked folder
					// over an unlinked one or vice versa. Fixes bug 28772.
					delete(existing, iterationMonitor.split(10));
					resource.copy(destinationPath, IResource.SHALLOW, iterationMonitor.split(90));
				}
			} else if (existing != null) {
				if (homogenousResources(resource, existing)) {
					copyExisting(resource, existing, iterationMonitor.split(100));
				} else {
					// Copying a linked resource over unlinked or vice
					// versa.
					// Can't use setContents here. Fixes bug 28772.
					delete(existing, iterationMonitor.split(10));
					iterationMonitor.setWorkRemaining(100);

					if ((createLinks || createVirtualFoldersAndLinks) && (resource.isLinked() == false)
							&& (resource.isVirtual() == false)) {
						if (resource.getType() == IResource.FILE) {
							IFile file = workspaceRoot.getFile(destinationPath);
							file.createLink(createRelativePath(resource.getLocationURI(), file), 0,
									iterationMonitor.split(100));
						} else {
							IFolder folder = workspaceRoot.getFolder(destinationPath);
							if (createVirtualFoldersAndLinks) {
								folder.create(IResource.VIRTUAL, true, iterationMonitor.split(1));
								IResource[] members = ((IContainer) resource).members();
								if (members.length > 0)
									copy(members, destinationPath, iterationMonitor.split(99));
							} else
								folder.createLink(createRelativePath(resource.getLocationURI(), folder), 0,
										iterationMonitor.split(100));
						}
					} else
						resource.copy(destinationPath, IResource.SHALLOW, iterationMonitor.split(100));
				}
			}
		}
	}

	/**
	 * Transform an absolute path URI to a relative path one (i.e. from
	 * "C:\foo\bar\file.txt" to "VAR\file.txt" granted that the relativeVariable
	 * is "VAR" and points to "C:\foo\bar\").
	 *
	 * @param locationURI
	 * @return an URI that was made relative to a variable
	 */
	private URI createRelativePath(URI locationURI, IResource resource) {
		if (relativeVariable == null)
			return locationURI;
		IPath location = URIUtil.toPath(locationURI);
		IPath result;
		try {
			result = URIUtil.toPath(resource.getPathVariableManager().convertToRelative(URIUtil.toURI(location), true, relativeVariable));
		} catch (CoreException e) {
			return locationURI;
		}
		return URIUtil.toURI(result);
	}

	/**
	 * Sets the content of the existing file to the source file content.
	 *
	 * @param source
	 *            source file to copy
	 * @param existing
	 *            existing file to set the source content in
	 * @param subMonitor
	 *            a progress monitor for showing progress and for cancelation
	 * @throws CoreException
	 *             setContents failed
	 */
	private void copyExisting(IResource source, IResource existing, IProgressMonitor monitor) throws CoreException {
		SubMonitor subMonitor = SubMonitor.convert(monitor, 1);
		IFile existingFile = getFile(existing);

		if (existingFile != null) {
			IFile sourceFile = getFile(source);

			if (sourceFile != null) {
				existingFile.setContents(sourceFile.getContents(), IResource.KEEP_HISTORY, subMonitor.split(1));
			}
		}
	}

	/**
	 * Copies the given resources to the destination. The current Thread is
	 * halted while the resources are copied using a WorkspaceModifyOperation.
	 * This method should be called from the UIThread.
	 *
	 * @param resources
	 *            the resources to copy
	 * @param destination
	 *            destination to which resources will be copied
	 * @return the resources which actually got copied
	 * @see WorkspaceModifyOperation
	 * @see Display#getThread()
	 * @see Thread#currentThread()
	 */
	public IResource[] copyResources(final IResource[] resources,
			IContainer destination) {
		return copyResources(resources, destination, true);
	}

	/**
	 * Copies the given resources to the destination in the current Thread
	 * without forking a new Thread or blocking using a
	 * WorkspaceModifyOperation. It recommended that this method only be called
	 * from a {@link WorkspaceJob} to avoid possible deadlock.
	 *
	 * @param resources
	 *            the resources to copy
	 * @param destination
	 *            destination to which resources will be copied
	 * @param monitor
	 *            the monitor that information will be sent to.
	 * @return IResource[] the resulting {@link IResource}[]
	 * @see WorkspaceModifyOperation
	 * @see WorkspaceJob
	 * @since 3.2
	 */
	public IResource[] copyResourcesInCurrentThread(
			final IResource[] resources, IContainer destination,
			IProgressMonitor monitor) {
		return copyResources(resources, destination, false);
	}

	/**
	 * Copies the given resources to the destination.
	 *
	 * @param resources
	 *            the resources to copy
	 * @param destination
	 *            destination to which resources will be copied
	 * @return IResource[] the resulting {@link IResource}[]
	 */
	private IResource[] copyResources(final IResource[] resources,
			IContainer destination, boolean fork) {
		final IPath destinationPath = destination.getFullPath();
		final IResource[][] copiedResources = new IResource[1][0];

		// test resources for existence separate from validate API.
		// Validate is performance critical and resource exists
		// check is potentially slow. Fixes bugs 16129/28602.
		IStatus resourceStatus = checkExist(resources);
		if (resourceStatus.getSeverity() != IStatus.OK) {
			displayError(resourceStatus);
			return copiedResources[0];
		}
		String errorMsg = validateDestination(destination, resources);
		if (errorMsg != null) {
			displayError(errorMsg);
			return copiedResources[0];
		}

		IRunnableWithProgress op = monitor -> copyResources(resources, destinationPath, copiedResources, monitor);

		try {
			PlatformUI.getWorkbench().getProgressService().run(fork, true, op);
		} catch (InterruptedException e) {
			return copiedResources[0];
		} catch (InvocationTargetException e) {
			display(e);
		}

		// If errors occurred, open an Error dialog
		if (errorStatus != null) {
			displayError(errorStatus);
			errorStatus = null;
		}

		return copiedResources[0];
	}

	/**
	 * Return whether the operation is a move or a copy
	 *
	 * @return whether the operation is a move or a copy
	 * @since 3.2
	 */
	protected boolean isMove() {
		return false;
	}

	private void display(InvocationTargetException e) {
		// CoreExceptions are collected above, but unexpected runtime
		// exceptions and errors may still occur.
		IDEWorkbenchPlugin.getDefault().getLog().log(
				StatusUtil.newStatus(IStatus.ERROR, MessageFormat.format(
						"Exception in {0}.performCopy(): {1}", //$NON-NLS-1$
						getClass().getName(), e.getTargetException()), null));
		displayError(NLS
				.bind(
						IDEWorkbenchMessages.CopyFilesAndFoldersOperation_internalError,
						e.getTargetException().getMessage()));
	}

	/**
	 * Copies the given URIS and folders to the destination. The current Thread
	 * is halted while the resources are copied using a
	 * WorkspaceModifyOperation. This method should be called from the UI
	 * Thread.
	 *
	 * @param uris
	 *            the URIs to copy
	 * @param destination
	 *            destination to which files will be copied
	 * @see WorkspaceModifyOperation
	 * @see Display#getThread()
	 * @see Thread#currentThread()
	 * @since 3.2
	 */
	public void copyFiles(URI[] uris, IContainer destination) {
		IFileStore[] stores = buildFileStores(uris);
		if (stores == null) {
			return;
		}

		copyFileStores(destination, stores, true, null);
	}

	/**
	 * Copies the given files and folders to the destination without forking a
	 * new Thread or blocking using a WorkspaceModifyOperation. It is
	 * recommended that this method only be called from a {@link WorkspaceJob}
	 * to avoid possible deadlock.
	 *
	 * @param uris
	 *            the URIs to copy
	 * @param destination
	 *            destination to which URIS will be copied
	 * @param monitor
	 *            the monitor that information will be sent to.
	 * @see WorkspaceModifyOperation
	 * @see WorkspaceJob
	 * @since 3.2
	 */
	public void copyFilesInCurrentThread(URI[] uris, IContainer destination,
			IProgressMonitor monitor) {
		IFileStore[] stores = buildFileStores(uris);
		if (stores == null) {
			return;
		}

		copyFileStores(destination, stores, false, monitor);
	}

	/**
	 * Build the collection of fileStores that map to fileNames. If any of them
	 * cannot be found then match then return <code>null</code>.
	 *
	 * @param uris
	 * @return IFileStore[]
	 */
	private IFileStore[] buildFileStores(URI[] uris) {
		IFileStore[] stores = new IFileStore[uris.length];
		for (int i = 0; i < uris.length; i++) {
			IFileStore store;
			try {
				store = EFS.getStore(uris[i]);
			} catch (CoreException e) {
				StatusManager.getManager().handle(e, IDEWorkbenchPlugin.IDE_WORKBENCH);
				reportFileInfoNotFound(uris[i].toString());
				return null;
			}
			if (store == null) {
				reportFileInfoNotFound(uris[i].toString());
				return null;
			}
			stores[i] = store;
		}
		return stores;

	}

	/**
	 * Depending on the 'Linked Resources' preferences it copies the given files and folders to the
	 * destination or creates links or shows a dialog that lets the user choose. The current thread
	 * is halted while the resources are copied using a {@link WorkspaceModifyOperation}. This
	 * method should be called from the UI Thread.
	 *
	 * @param fileNames names of the files to copy
	 * @param destination destination to which files will be copied
	 * @param dropOperation the drop operation ({@link DND#DROP_NONE}, {@link DND#DROP_MOVE}
	 *            {@link DND#DROP_COPY}, {@link DND#DROP_LINK}, {@link DND#DROP_DEFAULT})
	 * @see WorkspaceModifyOperation
	 * @see Display#getThread()
	 * @see Thread#currentThread()
	 * @since 3.6
	 */
	public void copyOrLinkFiles(final String[] fileNames, IContainer destination, int dropOperation) {
		IPreferenceStore store= IDEWorkbenchPlugin.getDefault().getPreferenceStore();
		boolean targetIsVirtual= destination.isVirtual();
		String dndPreference= store.getString(targetIsVirtual ? IDEInternalPreferences.IMPORT_FILES_AND_FOLDERS_VIRTUAL_FOLDER_MODE : IDEInternalPreferences.IMPORT_FILES_AND_FOLDERS_MODE);

		int mode= ImportTypeDialog.IMPORT_NONE;
		String variable= null;

		//check if resource linking is disabled
		if (ResourcesPlugin.getPlugin().getPluginPreferences().getBoolean(ResourcesPlugin.PREF_DISABLE_LINKING))
			mode= ImportTypeDialog.IMPORT_COPY;
		else if (dndPreference.equals(IDEInternalPreferences.IMPORT_FILES_AND_FOLDERS_MODE_PROMPT)) {
			ImportTypeDialog dialog= new ImportTypeDialog(messageShell, dropOperation, fileNames, destination);
			dialog.setResource(destination);
			if (dialog.open() == Window.OK) {
				mode= dialog.getSelection();
				variable= dialog.getVariable();
			}
		} else if (dndPreference.equals(IDEInternalPreferences.IMPORT_FILES_AND_FOLDERS_MODE_MOVE_COPY)) {
			mode= ImportTypeDialog.IMPORT_COPY;
		} else if (dndPreference.equals(IDEInternalPreferences.IMPORT_FILES_AND_FOLDERS_MODE_LINK)) {
			mode= ImportTypeDialog.IMPORT_LINK;
		} else if (dndPreference.equals(IDEInternalPreferences.IMPORT_FILES_AND_FOLDERS_MODE_LINK_AND_VIRTUAL_FOLDER)) {
			mode= ImportTypeDialog.IMPORT_VIRTUAL_FOLDERS_AND_LINKS;
		}

		switch (mode) {
			case ImportTypeDialog.IMPORT_COPY:
				copyFiles(fileNames, destination);
				break;
			case ImportTypeDialog.IMPORT_VIRTUAL_FOLDERS_AND_LINKS:
				if (variable != null)
					setRelativeVariable(variable);
				createVirtualFoldersAndLinks(fileNames, destination);
				break;
			case ImportTypeDialog.IMPORT_LINK:
				if (variable != null)
					setRelativeVariable(variable);
				linkFiles(fileNames, destination);
				break;
			case ImportTypeDialog.IMPORT_NONE:
				break;
		}

	}

	/**
	 * Copies the given files and folders to the destination. The current Thread is halted while the
	 * resources are copied using a WorkspaceModifyOperation. This method should be called from the
	 * UI Thread.
	 *
	 * @param fileNames names of the files to copy
	 * @param destination destination to which files will be copied
	 * @see WorkspaceModifyOperation
	 * @see Display#getThread()
	 * @see Thread#currentThread()
	 * @since 3.2
	 */
	public void copyFiles(final String[] fileNames, IContainer destination) {
		IFileStore[] stores = buildFileStores(fileNames);
		if (stores == null) {
			return;
		}

		copyFileStores(destination, stores, true, null);
	}

	/**
	 * Copies the given files and folders to the destination without forking a
	 * new Thread or blocking using a WorkspaceModifyOperation. It is
	 * recommended that this method only be called from a {@link WorkspaceJob}
	 * to avoid possible deadlock.
	 *
	 * @param fileNames
	 *            names of the files to copy
	 * @param destination
	 *            destination to which files will be copied
	 * @param monitor
	 *            the monitor that information will be sent to.
	 * @see WorkspaceModifyOperation
	 * @see WorkspaceJob
	 * @since 3.2
	 */
	public void copyFilesInCurrentThread(final String[] fileNames,
			IContainer destination, IProgressMonitor monitor) {
		IFileStore[] stores = buildFileStores(fileNames);
		if (stores == null) {
			return;
		}

		copyFileStores(destination, stores, false, monitor);
	}

	/**
	 * Build the collection of fileStores that map to fileNames. If any of them
	 * cannot be found then match then return null.
	 *
	 * @param fileNames
	 * @return IFileStore[]
	 */
	private IFileStore[] buildFileStores(final String[] fileNames) {
		IFileStore[] stores = new IFileStore[fileNames.length];
		for (int i = 0; i < fileNames.length; i++) {
			IFileStore store = IDEResourceInfoUtils.getFileStore(fileNames[i]);
			if (store == null) {
				reportFileInfoNotFound(fileNames[i]);
				return null;
			}
			stores[i] = store;
		}
		return stores;
	}

	/**
	 * Report that a file info could not be found.
	 *
	 * @param fileName
	 */
	private void reportFileInfoNotFound(final String fileName) {

		messageShell.getDisplay().syncExec(() -> ErrorDialog.openError(messageShell, getProblemsTitle(),
				NLS.bind(IDEWorkbenchMessages.CopyFilesAndFoldersOperation_infoNotFound, fileName), null));
	}

	/**
	 * Copies the given files and folders to the destination.
	 *
	 * @param stores
	 *            the file stores to copy
	 * @param destination
	 *            destination to which files will be copied
	 */
	private void copyFileStores(IContainer destination,
			final IFileStore[] stores, boolean fork, IProgressMonitor monitor) {
		// test files for existence separate from validate API
		// because an external file may not exist until the copy actually
		// takes place (e.g., WinZip contents).
		IStatus fileStatus = checkExist(stores);
		if (fileStatus.getSeverity() != IStatus.OK) {
			displayError(fileStatus);
			return;
		}
		String errorMsg = validateImportDestinationInternal(destination, stores);
		if (errorMsg != null) {
			displayError(errorMsg);
			return;
		}
		final IPath destinationPath = destination.getFullPath();

		if (fork) {
			WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
				@Override
				public void execute(IProgressMonitor monitor) {
					copyFileStores(stores, destinationPath, monitor);
				}
			};
			try {
				PlatformUI.getWorkbench().getProgressService().run(true, true,
						op);
			} catch (InterruptedException e) {
				return;
			} catch (InvocationTargetException exception) {
				display(exception);
			}
		} else {
			copyFileStores(stores, destinationPath, monitor);
		}

		// If errors occurred, open an Error dialog
		if (errorStatus != null) {
			displayError(errorStatus);
			errorStatus = null;
		}
	}

	/**
	 * Display the supplied status in an error dialog.
	 *
	 * @param status
	 *            The status to display
	 */
	private void displayError(final IStatus status) {
		messageShell.getDisplay().syncExec(() -> ErrorDialog.openError(messageShell, getProblemsTitle(), null, status));
	}

	/**
	 * Creates a file or folder handle for the source resource as if it were to
	 * be created in the destination container.
	 *
	 * @param destination
	 *            destination container
	 * @param source
	 *            source resource
	 * @return IResource file or folder handle, depending on the source type.
	 */
	IResource createLinkedResourceHandle(IContainer destination,
			IResource source) {
		IWorkspace workspace = destination.getWorkspace();
		IWorkspaceRoot workspaceRoot = workspace.getRoot();
		IPath linkPath = destination.getFullPath().append(source.getName());
		IResource linkHandle;

		if (source.getType() == IResource.FOLDER) {
			linkHandle = workspaceRoot.getFolder(linkPath);
		} else {
			linkHandle = workspaceRoot.getFile(linkPath);
		}
		return linkHandle;
	}

	/**
	 * Removes the given resource from the workspace.
	 *
	 * @param resource
	 *            resource to remove from the workspace
	 * @param monitor
	 *            a progress monitor for showing progress and for cancelation
	 * @return true the resource was deleted successfully false the resource was
	 *         not deleted because a CoreException occurred
	 */
	boolean delete(IResource resource, IProgressMonitor monitor) {
		boolean force = false; // don't force deletion of out-of-sync resources

		if (resource.getType() == IResource.PROJECT) {
			// if it's a project, ask whether content should be deleted too
			IProject project = (IProject) resource;
			try {
				project.delete(true, force, monitor);
			} catch (CoreException e) {
				recordError(e); // log error
				return false;
			}
		} else {
			// if it's not a project, just delete it
			int flags = IResource.KEEP_HISTORY;
			if (force) {
				flags = flags | IResource.FORCE;
			}
			try {
				resource.delete(flags, monitor);
			} catch (CoreException e) {
				recordError(e); // log error
				return false;
			}
		}
		return true;
	}

	/**
	 * Opens an error dialog to display the given message.
	 *
	 * @param message
	 *            the error message to show
	 */
	private void displayError(final String message) {
		messageShell.getDisplay().syncExec(() -> MessageDialog.openError(messageShell, getProblemsTitle(), message));
	}

	/**
	 * Returns the resource either casted to or adapted to an IFile.
	 *
	 * @param resource
	 *            resource to cast/adapt
	 * @return the resource either casted to or adapted to an IFile.
	 *         <code>null</code> if the resource does not adapt to IFile
	 */
	protected IFile getFile(IResource resource) {
		return Adapters.adapt(resource, IFile.class);
	}

	/**
	 * Returns java.io.File objects for the given file names.
	 *
	 * @param fileNames
	 *            files to return File object for.
	 * @return java.io.File objects for the given file names.
	 * @deprecated As of 3.3, this method is no longer in use anywhere in this
	 *             class and is only provided for backwards compatability with
	 *             subclasses of the receiver.
	 */
	@Deprecated
	protected File[] getFiles(String[] fileNames) {
		File[] files = new File[fileNames.length];

		for (int i = 0; i < fileNames.length; i++) {
			files[i] = new File(fileNames[i]);
		}
		return files;
	}

	/**
	 * Returns the resource either casted to or adapted to an IFolder.
	 *
	 * @param resource
	 *            resource to cast/adapt
	 * @return the resource either casted to or adapted to an IFolder.
	 *         <code>null</code> if the resource does not adapt to IFolder
	 */
	protected IFolder getFolder(IResource resource) {
		return Adapters.adapt(resource, IFolder.class);
	}

	/**
	 * Returns a new name for a copy of the resource at the given path in the
	 * given workspace.
	 *
	 * @param originalName
	 *            the full path of the resource
	 * @param workspace
	 *            the workspace
	 * @return the new full path for the copy, or <code>null</code> if the
	 *         resource should not be copied
	 */
	private IPath getNewNameFor(final IPath originalName,
			final IWorkspace workspace) {
		final IResource resource = workspace.getRoot().findMember(originalName);
		final IPath prefix = resource.getFullPath().removeLastSegments(1);
		final String returnValue[] = { "" }; //$NON-NLS-1$

		messageShell.getDisplay().syncExec(() -> {
			IInputValidator validator = new IInputValidator() {
				@Override
				public String isValid(String string) {
					if (resource.getName().equals(string)) {
						return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_nameMustBeDifferent;
					}
					IStatus status = workspace.validateName(string, resource.getType());
					if (!status.isOK()) {
						return status.getMessage();
					}
					if (workspace.getRoot().exists(prefix.append(string))) {
						return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_nameExists;
					}
					return null;
				}
			};

			final String initial = getAutoNewNameFor(originalName, workspace).lastSegment();
			InputDialog dialog = new InputDialog(messageShell,
					IDEWorkbenchMessages.CopyFilesAndFoldersOperation_inputDialogTitle,
					NLS.bind(IDEWorkbenchMessages.CopyFilesAndFoldersOperation_inputDialogMessage, resource.getName()),
					initial, validator) {

				@Override
				protected Control createContents(Composite parent) {
					Control contents = super.createContents(parent);
					int lastIndexOfDot = initial.lastIndexOf('.');
					if (resource instanceof IFile && lastIndexOfDot > 0) {
						getText().setSelection(0, lastIndexOfDot);
					}
					return contents;
				}
			};
			dialog.setBlockOnOpen(true);
			dialog.open();
			if (dialog.getReturnCode() == Window.CANCEL) {
				returnValue[0] = null;
			} else {
				returnValue[0] = dialog.getValue();
			}
		});
		if (returnValue[0] == null) {
			throw new OperationCanceledException();
		}
		return prefix.append(returnValue[0]);
	}

	/**
	 * Returns the task title for this operation's progress dialog.
	 *
	 * @return the task title
	 */
	protected String getOperationTitle() {
		return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_operationTitle;
	}

	/**
	 * Returns the message for this operation's problems dialog.
	 *
	 * @return the problems message
	 */
	protected String getProblemsMessage() {
		return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_problemMessage;
	}

	/**
	 * Returns the title for this operation's problems dialog.
	 *
	 * @return the problems dialog title
	 */
	protected String getProblemsTitle() {
		return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_copyFailedTitle;
	}

	/**
	 * Returns whether the source file in a destination collision will be
	 * validateEdited together with the collision itself. Returns false. Should
	 * return true if the source file is to be deleted after the operation.
	 *
	 * @return boolean <code>true</code> if the source file in a destination
	 *         collision should be validateEdited. <code>false</code> if only
	 *         the destination should be validated.
	 */
	protected boolean getValidateConflictSource() {
		return false;
	}

	/**
	 * Returns whether the given resources are either both linked or both
	 * unlinked.
	 *
	 * @param source
	 *            source resource
	 * @param destination
	 *            destination resource
	 * @return boolean <code>true</code> if both resources are either linked
	 *         or unlinked. <code>false</code> otherwise.
	 */
	protected boolean homogenousResources(IResource source,
			IResource destination) {
		boolean isSourceLinked = source.isLinked();
		boolean isDestinationLinked = destination.isLinked();

		return (isSourceLinked && isDestinationLinked || isSourceLinked == false
				&& isDestinationLinked == false);
	}

	/**
	 * Returns whether the given resource is accessible. Files and folders are
	 * always considered accessible and a project is accessible if it is open.
	 *
	 * @param resource
	 *            the resource
	 * @return <code>true</code> if the resource is accessible, and
	 *         <code>false</code> if it is not
	 */
	private boolean isAccessible(IResource resource) {
		switch (resource.getType()) {
		case IResource.FILE:
			return true;
		case IResource.FOLDER:
			return true;
		case IResource.PROJECT:
			return ((IProject) resource).isOpen();
		default:
			return false;
		}
	}

	/**
	 * Returns whether any of the given source resources are being recopied to
	 * their current container.
	 *
	 * @param sourceResources
	 *            the source resources
	 * @param destination
	 *            the destination container
	 * @return <code>true</code> if at least one of the given source
	 *         resource's parent container is the same as the destination
	 */
	boolean isDestinationSameAsSource(IResource[] sourceResources,
			IContainer destination) {
		IPath destinationLocation = destination.getLocation();

		for (IResource resource : sourceResources) {
			if (resource.getParent().equals(destination)) {
				return true;
			} else if (destinationLocation != null) {
				// do thorough check to catch linked resources. Fixes bug 29913.
				IPath sourceLocation = resource.getLocation();
				IPath destinationResource = destinationLocation.append(resource.getName());
				if (sourceLocation != null
						&& sourceLocation.isPrefixOf(destinationResource)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Copies the given resources to the destination container with the given
	 * name.
	 * <p>
	 * Note: the destination container may need to be created prior to copying
	 * the resources.
	 * </p>
	 *
	 * @param resources
	 *            the resources to copy
	 * @param destination
	 *            the path of the destination container
	 * @param monitor
	 *            a progress monitor for showing progress and for cancelation
	 * @return <code>true</code> if the copy operation completed without
	 *         errors
	 */
	private boolean performCopy(IResource[] resources, IPath destination,
			IProgressMonitor monitor) {
		try {
			AbstractWorkspaceOperation op = getUndoableCopyOrMoveOperation(
					resources, destination);
			op.setModelProviderIds(getModelProviderIds());
			if (op instanceof CopyResourcesOperation) {
				CopyResourcesOperation copyMoveOp = (CopyResourcesOperation) op;
				copyMoveOp.setCreateVirtualFolders(createVirtualFoldersAndLinks);
				copyMoveOp.setCreateLinks(createLinks);
				copyMoveOp.setRelativeVariable(relativeVariable);
			}
			// If we are copying files and folders, do not execute the operation
			// in the undo history, since the creation of a new file is not
			// added to undo history and modification of a file is not added to
			// the same undo history and therefore a redo cannot be properly
			// done. Just execute it directly so it won't be added to the undo
			// history.
			op.execute(monitor, WorkspaceUndoUtil.getUIInfoAdapter(messageShell));
		} catch (ExecutionException e) {
			if (e.getCause() instanceof CoreException) {
				recordError((CoreException) e.getCause());
			} else {
				IDEWorkbenchPlugin.log(e.getMessage(), e);
				displayError(e.getMessage());
			}
			return false;
		}
		return true;
	}

	/**
	 * Individually copies the given resources to the specified destination
	 * container checking for name collisions. If a collision is detected, it is
	 * saved with a new name.
	 * <p>
	 * Note: the destination container may need to be created prior to copying
	 * the resources.
	 * </p>
	 *
	 * @param resources
	 *            the resources to copy
	 * @param destination
	 *            the path of the destination container
	 * @return <code>true</code> if the copy operation completed without
	 *         errors.
	 */
	private boolean performCopyWithAutoRename(IResource[] resources,
			IPath destination, IProgressMonitor monitor) {
		IWorkspace workspace = resources[0].getWorkspace();
		IPath[] destinationPaths = new IPath[resources.length];
		try {
			for (int i = 0; i < resources.length; i++) {
				IResource source = resources[i];
				destinationPaths[i] = destination.append(source.getName());
				if (source.getType() != IResource.FILE) {
					destinationPaths[i] = destinationPaths[i].addTrailingSeparator();
				}

				if (workspace.getRoot().exists(destinationPaths[i])) {
					destinationPaths[i] = getNewNameFor(destinationPaths[i],
							workspace);
				}
			}
			CopyResourcesOperation op = new CopyResourcesOperation(resources,
					destinationPaths,
					IDEWorkbenchMessages.CopyFilesAndFoldersOperation_copyTitle);
			op.setModelProviderIds(getModelProviderIds());
			// If we are copying files and folders, do not execute the operation
			// in the undo history, since the creation of a new file is not
			// added to undo history and modification of a file is not added to
			// the same undo history and therefore a redo cannot be properly
			// done. Just execute it directly so it won't be added to the undo
			// history.
			op.execute(monitor, WorkspaceUndoUtil.getUIInfoAdapter(messageShell));
		} catch (ExecutionException e) {
			if (e.getCause() instanceof CoreException) {
				recordError((CoreException) e.getCause());
			} else {
				IDEWorkbenchPlugin.log(e.getMessage(), e);
				displayError(e.getMessage());
			}
			return false;
		}
		return true;
	}

	/**
	 * Performs an import of the given stores into the provided container.
	 * Returns a status indicating if the import was successful.
	 *
	 * @param stores
	 *            stores that are to be imported
	 * @param target
	 *            container to which the import will be done
	 * @param monitor
	 *            a progress monitor for showing progress and for cancelation
	 */
	private void performFileImport(IFileStore[] stores, IContainer target,
			IProgressMonitor monitor) {
		IOverwriteQuery query = new IOverwriteQuery() {
			@Override
			public String queryOverwrite(String pathString) {
				if (alwaysOverwrite) {
					return ALL;
				}

				final String returnCode[] = { CANCEL };
				final String msg = NLS.bind(IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteQuestion,
						pathString);
				final String[] options = { IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteButtonLabel,
						IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteAllButtonLabel,
						IDEWorkbenchMessages.CopyFilesAndFoldersOperation_dontOverwriteButtonLabel,
						IDialogConstants.CANCEL_LABEL };
				messageShell.getDisplay().syncExec(() -> {
					MessageDialog dialog = new MessageDialog(messageShell,
							IDEWorkbenchMessages.CopyFilesAndFoldersOperation_question, null, msg,
							MessageDialog.QUESTION, 0, options) {
						@Override
						protected int getShellStyle() {
							return super.getShellStyle() | SWT.SHEET;
						}
					};
					dialog.open();
					int returnVal = dialog.getReturnCode();
					String[] returnCodes = { YES, ALL, NO, CANCEL };
					returnCode[0] = returnVal == -1 ? CANCEL : returnCodes[returnVal];
				});
				if (returnCode[0] == ALL) {
					alwaysOverwrite = true;
				} else if (returnCode[0] == CANCEL) {
					canceled = true;
				}
				return returnCode[0];
			}
		};

		ImportOperation op = new ImportOperation(target.getFullPath(),
				stores[0].getParent(), FileStoreStructureProvider.INSTANCE,
				query, Arrays.asList(stores));
		op.setContext(messageShell);
		op.setCreateContainerStructure(false);
		op.setVirtualFolders(createVirtualFoldersAndLinks);
		op.setCreateLinks(createLinks);
		op.setRelativeVariable(relativeVariable);
		try {
			op.run(monitor);
		} catch (InterruptedException e) {
			return;
		} catch (InvocationTargetException e) {
			if (e.getTargetException() instanceof CoreException) {
				displayError(((CoreException) e.getTargetException())
						.getStatus());
			} else {
				display(e);
			}
			return;
		}
		// Special case since ImportOperation doesn't throw a CoreException on
		// failure.
		IStatus status = op.getStatus();
		if (!status.isOK()) {
			if (errorStatus == null) {
				errorStatus = new MultiStatus(PlatformUI.PLUGIN_ID,
						IStatus.ERROR, getProblemsMessage(), null);
			}
			errorStatus.merge(status);
		}
	}

	/**
	 * Records the core exception to be displayed to the user once the action is
	 * finished.
	 *
	 * @param error
	 *            a <code>CoreException</code>
	 */
	private void recordError(CoreException error) {
		if (errorStatus == null) {
			errorStatus = new MultiStatus(PlatformUI.PLUGIN_ID, IStatus.ERROR,
					getProblemsMessage(), error);
		}

		errorStatus.merge(error.getStatus());
	}

	/**
	 * Checks whether the destination is valid for copying the source resources.
	 * <p>
	 * Note this method is for internal use only. It is not API.
	 * </p>
	 *
	 * @param destination
	 *            the destination container
	 * @param sourceResources
	 *            the source resources
	 * @return an error message, or <code>null</code> if the path is valid
	 */
	public String validateDestination(IContainer destination,
			IResource[] sourceResources) {
		if (!isAccessible(destination)) {
			return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_destinationAccessError;
		}
		IContainer firstParent = null;
		URI destinationLocation = destination.getLocationURI();
		for (IResource sourceResource : sourceResources) {
			if (firstParent == null) {
				firstParent = sourceResource.getParent();
			} else if (firstParent.equals(sourceResource.getParent()) == false) {
				// Resources must have common parent. Fixes bug 33398.
				return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_parentNotEqual;
			}

			// verify that if the destination is a virtual folder, the resource must be
			// either a link or another virtual folder
			if (destination.isVirtual()) {
				if (!sourceResource.isLinked() && !sourceResource.isVirtual()
						&& !createLinks && !createVirtualFoldersAndLinks) {
					return NLS
							.bind(
									IDEWorkbenchMessages.CopyFilesAndFoldersOperation_sourceCannotBeCopiedIntoAVirtualFolder,
									sourceResource.getName());
				}
			}
			URI sourceLocation = sourceResource.getLocationURI();
			if (sourceLocation == null) {
				if (sourceResource.isLinked()) {
					// Don't allow copying linked resources with undefined path
					// variables. See bug 28754.
					return NLS
							.bind(
									IDEWorkbenchMessages.CopyFilesAndFoldersOperation_missingPathVariable,
									sourceResource.getName());
				}
				return NLS
						.bind(
								IDEWorkbenchMessages.CopyFilesAndFoldersOperation_resourceDeleted,
								sourceResource.getName());

			}
			if (!destination.isVirtual()) {
				if (sourceLocation.equals(destinationLocation)) {
					return NLS
							.bind(
									IDEWorkbenchMessages.CopyFilesAndFoldersOperation_sameSourceAndDest,
									sourceResource.getName());
				}
				// is the source a parent of the destination?
				if (new Path(sourceLocation.toString()).isPrefixOf(new Path(
						destinationLocation.toString()))) {
					return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_destinationDescendentError;
				}
			}

			String linkedResourceMessage = validateLinkedResource(destination,
					sourceResource);
			if (linkedResourceMessage != null) {
				return linkedResourceMessage;
			}
		}
		return null;
	}

	/**
	 * Validates that the given source resources can be copied to the
	 * destination as decided by the VCM provider.
	 *
	 * @param destination
	 *            copy destination
	 * @param sourceResources
	 *            source resources
	 * @return <code>true</code> all files passed validation or there were no
	 *         files to validate. <code>false</code> one or more files did not
	 *         pass validation.
	 */
	private boolean validateEdit(IContainer destination,
			IResource[] sourceResources) {
		ArrayList<IFile> copyFiles = new ArrayList<>();

		collectExistingReadonlyFiles(destination.getFullPath(),
				sourceResources, copyFiles);
		if (copyFiles.size() > 0) {
			IFile[] files = copyFiles.toArray(new IFile[copyFiles
					.size()]);
			IWorkspace workspace = ResourcesPlugin.getWorkspace();
			IStatus status = workspace.validateEdit(files, messageShell);

			canceled = status.isOK() == false;
			return status.isOK();
		}
		return true;
	}

	/**
	 * Checks whether the destination is valid for copying the source files.
	 * <p>
	 * Note this method is for internal use only. It is not API.
	 * </p>
	 *
	 * @param destination
	 *            the destination container
	 * @param sourceNames
	 *            the source file names
	 * @return an error message, or <code>null</code> if the path is valid
	 */
	public String validateImportDestination(IContainer destination,
			String[] sourceNames) {

		IFileStore[] stores = new IFileStore[sourceNames.length];
		for (int i = 0; i < sourceNames.length; i++) {
			IFileStore store = IDEResourceInfoUtils
					.getFileStore(sourceNames[i]);
			if (store == null) {
				return NLS
						.bind(
								IDEWorkbenchMessages.CopyFilesAndFoldersOperation_infoNotFound,
								sourceNames[i]);
			}
			stores[i] = store;
		}
		return validateImportDestinationInternal(destination, stores);

	}

	/**
	 * Checks whether the destination is valid for copying the source file
	 * stores.
	 * <p>
	 * Note this method is for internal use only. It is not API.
	 * </p>
	 * <p>
	 * TODO Bug 117804. This method has been renamed to avoid a bug in the
	 * Eclipse compiler with regards to visibility and type resolution when
	 * linking.
	 * </p>
	 *
	 * @param destination
	 *            the destination container
	 * @param sourceStores
	 *            the source IFileStore
	 * @return an error message, or <code>null</code> if the path is valid
	 */
	private String validateImportDestinationInternal(IContainer destination,
			IFileStore[] sourceStores) {
		if (!isAccessible(destination))
			return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_destinationAccessError;

		if (!destination.isVirtual()) {
			IFileStore destinationStore;
			try {
				destinationStore = EFS.getStore(destination.getLocationURI());
			} catch (CoreException exception) {
				IDEWorkbenchPlugin.log(exception.getLocalizedMessage(), exception);
				return NLS
						.bind(
								IDEWorkbenchMessages.CopyFilesAndFoldersOperation_internalError,
								exception.getLocalizedMessage());
			}
			for (IFileStore fileStore : sourceStores) {
				IFileStore parentFileStore = fileStore.getParent();

				if (fileStore != null) {
					if (destinationStore.equals(fileStore)
							|| (parentFileStore != null && destinationStore
							.equals(parentFileStore))) {
						return NLS
								.bind(
										IDEWorkbenchMessages.CopyFilesAndFoldersOperation_importSameSourceAndDest,
										fileStore.getName());
					}
					// work around bug 16202. replacement for
					// sourcePath.isPrefixOf(destinationPath)
					if (fileStore.isParentOf(destinationStore)) {
						return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_destinationDescendentError;
					}
				}
			}
		}
		return null;
	}

	/**
	 * Check if the destination is valid for the given source resource.
	 *
	 * @param destination
	 *            destination container of the operation
	 * @param source
	 *            source resource
	 * @return String error message or null if the destination is valid
	 */
	private String validateLinkedResource(IContainer destination,
			IResource source) {
		if ((source.isLinked() == false) || source.isVirtual()) {
			return null;
		}
		IWorkspace workspace = destination.getWorkspace();
		IResource linkHandle = createLinkedResourceHandle(destination, source);
		IStatus locationStatus = workspace.validateLinkLocationURI(linkHandle,
				source.getRawLocationURI());

		if (locationStatus.getSeverity() == IStatus.ERROR) {
			return locationStatus.getMessage();
		}
		IPath sourceLocation = source.getLocation();
		if (source.getProject().equals(destination.getProject()) == false
				&& source.getType() == IResource.FOLDER
				&& sourceLocation != null) {
			// prevent merging linked folders that point to the same
			// file system folder
			try {
				for (IResource resource : destination.members()) {
					if (sourceLocation.equals(resource.getLocation()) && source.getName().equals(resource.getName())) {
						return NLS
								.bind(
										IDEWorkbenchMessages.CopyFilesAndFoldersOperation_sameSourceAndDest,
										source.getName());
					}
				}
			} catch (CoreException exception) {
				displayError(NLS
						.bind(
								IDEWorkbenchMessages.CopyFilesAndFoldersOperation_internalError,
								exception.getMessage()));
			}
		}
		return null;
	}

	/**
	 * Returns whether moving all of the given source resources to the given
	 * destination container could be done without causing name collisions.
	 *
	 * @param destination
	 *            the destination container
	 * @param sourceResources
	 *            the list of resources
	 * @return <code>true</code> if there would be no name collisions, and
	 *         <code>false</code> if there would
	 */
	private IResource[] validateNoNameCollisions(IContainer destination,
			IResource[] sourceResources) {
		List<IResource> copyItems = new ArrayList<>();
		IWorkspaceRoot workspaceRoot = destination.getWorkspace().getRoot();
		int overwrite = IDialogConstants.NO_ID;

		// Check to see if we would be overwriting a parent folder.
		// Cancel entire copy operation if we do.
		for (final IResource resource : sourceResources) {
			final IPath destinationPath = destination.getFullPath().append(
					resource.getName());
			final IPath sourcePath = resource.getFullPath();

			IResource newResource = workspaceRoot.findMember(destinationPath);
			if (newResource != null && destinationPath.isPrefixOf(sourcePath)) {
				displayError(NLS
						.bind(
								IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteProblem,
								destinationPath, sourcePath));

				canceled = true;
				return null;
			}
		}
		// Check for overwrite conflicts
		for (final IResource resource : sourceResources) {
			final IPath destinationPath = destination.getFullPath().append(
					resource.getName());

			IResource newResource = workspaceRoot.findMember(destinationPath);
			if (newResource != null) {
				if (overwrite != IDialogConstants.YES_TO_ALL_ID
						|| (newResource.getType() == IResource.FOLDER && homogenousResources(
								resource, destination) == false)) {
					overwrite = checkOverwrite(resource, newResource);
				}
				if (overwrite == IDialogConstants.YES_ID
						|| overwrite == IDialogConstants.YES_TO_ALL_ID) {
					copyItems.add(resource);
				} else if (overwrite == IDialogConstants.CANCEL_ID) {
					canceled = true;
					return null;
				}
			} else {
				copyItems.add(resource);
			}
		}
		return copyItems.toArray(new IResource[copyItems.size()]);
	}

	private void copyResources(final IResource[] resources, final IPath destinationPath,
			final IResource[][] copiedResources, IProgressMonitor mon) {
		IResource[] copyResources = resources;

		// Fix for bug 31116. Do not provide a task name when
		// creating the task.
		SubMonitor subMonitor = SubMonitor.convert(mon, 100);
		subMonitor.setTaskName(getOperationTitle());
		subMonitor.worked(10); // show some initial progress

		// Checks only required if this is an exisiting container path.
		boolean copyWithAutoRename = false;
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		if (root.exists(destinationPath)) {
			IContainer container = (IContainer) root
					.findMember(destinationPath);
			// If we're copying to the source container then perform
			// auto-renames on all resources to avoid name collisions.
			if (isDestinationSameAsSource(copyResources, container)
					&& canPerformAutoRename()) {
				copyWithAutoRename = true;
			} else {
				// If no auto-renaming will be happening, check for
				// potential name collisions at the target resource
				copyResources = validateNoNameCollisions(container,
						copyResources);
				if (copyResources == null) {
					if (canceled) {
						return;
					}
					displayError(IDEWorkbenchMessages.CopyFilesAndFoldersOperation_nameCollision);
					return;
				}
				if (validateEdit(container, copyResources) == false) {
					return;
				}
			}
		}

		errorStatus = null;
		if (copyResources.length > 0) {
			if (copyWithAutoRename) {
				performCopyWithAutoRename(copyResources, destinationPath, subMonitor.split(90));
			} else {
				performCopy(copyResources, destinationPath, subMonitor.split(90));
			}
		}
		copiedResources[0] = copyResources;
	}

	private void copyFileStores(final IFileStore[] stores,
			final IPath destinationPath, IProgressMonitor monitor) {
		// Checks only required if this is an exisiting container path.
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		if (root.exists(destinationPath)) {
			IContainer container = (IContainer) root
					.findMember(destinationPath);

			performFileImport(stores, container, monitor);
		}
	}

	/**
	 * Returns the model provider ids that are known to the client that
	 * instantiated this operation.
	 *
	 * @return the model provider ids that are known to the client that
	 *         instantiated this operation.
	 * @since 3.2
	 */
	public String[] getModelProviderIds() {
		return modelProviderIds;
	}

	/**
	 * Sets the model provider ids that are known to the client that
	 * instantiated this operation. Any potential side effects reported by these
	 * models during validation will be ignored.
	 *
	 * @param modelProviderIds
	 *            the model providers known to the client who is using this
	 *            operation.
	 * @since 3.2
	 */
	public void setModelProviderIds(String[] modelProviderIds) {
		this.modelProviderIds = modelProviderIds;
	}

	/**
	 * Create virtual folders and links of the given files and folders to the
	 * destination. The current Thread is halted while the resources are copied
	 * using a WorkspaceModifyOperation. This method should be called from the
	 * UI Thread.
	 *
	 * @param fileNames
	 *            names of the files to copy
	 * @param destination
	 *            destination to which files will be copied
	 * @see WorkspaceModifyOperation
	 * @see Display#getThread()
	 * @see Thread#currentThread()
	 * @since 3.6
	 */
	public void createVirtualFoldersAndLinks(final String[] fileNames,
			IContainer destination) {
		IFileStore[] stores = buildFileStores(fileNames);
		if (stores == null) {
			return;
		}

		createVirtualFoldersAndLinks = true;
		copyFileStores(destination, stores, true, null);
	}

	/**
	 * Create links of the given files and folders to the destination. The
	 * current Thread is halted while the resources are copied using a
	 * WorkspaceModifyOperation. This method should be called from the UI
	 * Thread.
	 *
	 * @param fileNames
	 *            names of the files to copy
	 * @param destination
	 *            destination to which files will be copied
	 * @see WorkspaceModifyOperation
	 * @see Display#getThread()
	 * @see Thread#currentThread()
	 * @since 3.6
	 */
	public void linkFiles(final String[] fileNames, IContainer destination) {
		IFileStore[] stores = buildFileStores(fileNames);
		if (stores == null) {
			return;
		}

		createLinks = true;
		copyFileStores(destination, stores, true, null);
	}

	/**
	 * Set whether or not virtual folders and links will be created under the
	 * destination container.
	 *
	 * @param value <code>true</code> to create virtual folders and links under
	 *              destination container
	 * @since 3.6
	 */
	public void setVirtualFolders(boolean value) {
		createVirtualFoldersAndLinks = value;
	}

	/**
	 * Set whether or not links will be created under the destination container.
	 *
	 * @param value <code>true</code> to create links under destination container
	 * @since 3.6
	 */
	public void setCreateLinks(boolean value) {
		createLinks = value;
	}

	/**
	 * Set a variable relative to which the links are created
	 *
	 * @param variable base for relative links
	 * @since 3.6
	 */
	public void setRelativeVariable(String variable) {
		relativeVariable = variable;
	}

	/**
	 * Returns an AbstractWorkspaceOperation suitable for performing the move or
	 * copy operation that will move or copy the given resources to the given
	 * destination path.
	 *
	 * @param resources
	 *            the resources to be moved or copied
	 * @param destinationPath
	 *            the destination path to which the resources should be moved
	 * @return the operation that should be used to perform the move or cop
	 * @since 3.3
	 */
	protected AbstractWorkspaceOperation getUndoableCopyOrMoveOperation(
			IResource[] resources, IPath destinationPath) {
		return new CopyResourcesOperation(resources, destinationPath,
				IDEWorkbenchMessages.CopyFilesAndFoldersOperation_copyTitle);

	}
}
